package com.ibm.cps.sql.netsfparser;

import net.sf.jsqlparser.expression.AllComparisonExpression;
import net.sf.jsqlparser.expression.AnalyticExpression;
import net.sf.jsqlparser.expression.AnyComparisonExpression;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.CaseExpression;
import net.sf.jsqlparser.expression.CastExpression;
import net.sf.jsqlparser.expression.DateValue;
import net.sf.jsqlparser.expression.DoubleValue;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.ExtractExpression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.IntervalExpression;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.JsonExpression;
import net.sf.jsqlparser.expression.KeepExpression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.NumericBind;
import net.sf.jsqlparser.expression.OracleHierarchicalExpression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.SignedExpression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.TimeValue;
import net.sf.jsqlparser.expression.TimestampValue;
import net.sf.jsqlparser.expression.UserVariable;
import net.sf.jsqlparser.expression.WhenClause;
import net.sf.jsqlparser.expression.WithinGroupExpression;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseOr;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor;
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
import net.sf.jsqlparser.expression.operators.arithmetic.Division;
import net.sf.jsqlparser.expression.operators.arithmetic.Modulo;
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.Matches;
import net.sf.jsqlparser.expression.operators.relational.RegExpMatchOperator;
import net.sf.jsqlparser.expression.operators.relational.RegExpMySQLOperator;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.SubSelect;

import com.ibm.cps.sql.expression.ArrayVisitingNode;
import com.ibm.cps.sql.expression.CalculationNode;
import com.ibm.cps.sql.expression.FunctionNode;
import com.ibm.cps.sql.expression.IdentifierNode;
import com.ibm.cps.sql.expression.NumberNode;
import com.ibm.cps.sql.expression.StringLiteralNode;
import com.ibm.cps.sql.expression.ValueExpression;
import com.ibm.cps.sql.expression.function.FunctionRegister;

public class ValueExpressionParser extends ExpressionVisitorAdapter{
	private ValueExpression expression;
	
	public ValueExpression getExpression() {
		return expression;
	}

	@Override
	public void visit(NullValue arg0) {
	}

	//function invoking and array visiting shares a same grammar <name>(<parameter>)
	@Override
	public void visit(Function func) {
		String name = func.getName();
		ExpressionList args = func.getParameters();
		if(isValidFunctionName(name)){
			ValueExpression[] argNodes = new ValueExpression[args.getExpressions().size()];
			for(int i = 0; i < argNodes.length; i++){
				Expression ex = args.getExpressions().get(i);
				ValueExpressionParser parser = new ValueExpressionParser();
				ex.accept(parser);
				argNodes[i] = parser.getExpression();
			}
			expression = new FunctionNode(name, argNodes);
		}else{
			if(args.getExpressions().size() != 1)
				throw new IllegalArgumentException("Invalid array visiting expression " + func + " only one parameter (index) allowed");
			Expression indexExpression = args.getExpressions().get(0);
			ValueExpressionParser parser = new ValueExpressionParser();
			indexExpression.accept(parser);
			ValueExpression index = parser.getExpression();
			if(!(index instanceof NumberNode)){
				throw new IllegalArgumentException("Invalid array visiting expression " + func + " only integer value allowed for array index");
			}
			NumberNode number = (NumberNode)index;
			double rounded = Math.round(number.getValue());
			if(rounded != number.getValue()){
				throw new IllegalArgumentException("Invalid array visiting expression " + func + " only integer value allowed for array index");
			}
			expression = new ArrayVisitingNode(name, (int)rounded);
		}
	}
	
	private boolean isValidFunctionName(String name){
		name = name.toLowerCase();
		return (FunctionRegister.findScalarFunction(name) != null) 
				|| (FunctionRegister.findVectorFunction(name) != null);
	}

	@Override
	public void visit(SignedExpression arg0) {
		
	}

	@Override
	public void visit(JdbcParameter arg0) {
		
	}

	@Override
	public void visit(JdbcNamedParameter arg0) {
		
	}

	@Override
	public void visit(DoubleValue arg0) {
		this.expression = new NumberNode(arg0.getValue());
	}

	@Override
	public void visit(LongValue arg0) {
		this.expression = new NumberNode(arg0.getValue());
	}

	@Override
	public void visit(DateValue arg0) {
		
	}

	@Override
	public void visit(TimeValue arg0) {
		
	}

	@Override
	public void visit(TimestampValue arg0) {
		
	}

	@Override
	public void visit(Parenthesis arg0) {
		Expression innerExperssion = arg0.getExpression();
		ValueExpressionParser parser = new ValueExpressionParser();
		innerExperssion.accept(parser);
		this.expression = parser.getExpression();
	}

	@Override
	public void visit(StringValue arg0) {
		this.expression = new StringLiteralNode(arg0.getValue());
	}

	@Override
	public void visit(Addition addition) {
		this.expression = parseCalculationExpression(CalculationNode.Operator.PLUS, addition);
	}

	@Override
	public void visit(Division division) {
		this.expression = parseCalculationExpression(CalculationNode.Operator.DIVIDE, division);
	}

	@Override
	public void visit(Multiplication multiplication) {
		this.expression = parseCalculationExpression(CalculationNode.Operator.TIMES, multiplication);
	}

	@Override
	public void visit(Subtraction substraction) {
		this.expression = parseCalculationExpression(CalculationNode.Operator.MINUS, substraction);
	}
	
	private ValueExpression parseCalculationExpression(CalculationNode.Operator operator, BinaryExpression expression){
		ValueExpressionParser parser = new ValueExpressionParser();
		expression.getLeftExpression().accept(parser);
		ValueExpression leftNode = parser.getExpression();
		expression.getRightExpression().accept(parser);
		ValueExpression rightNode = parser.getExpression();
		return new CalculationNode(operator, leftNode, rightNode);
	}

	@Override
	public void visit(InExpression arg0) {
		
	}

	@Override
	public void visit(IsNullExpression arg0) {
		
	}

	@Override
	public void visit(LikeExpression arg0) {
		
	}

	@Override
	public void visit(Column arg0) {
		String fullQualifiedName = arg0.getFullyQualifiedName();
		this.expression = new IdentifierNode(fullQualifiedName);
	}

	@Override
	public void visit(SubSelect arg0) {
		
	}

	@Override
	public void visit(CaseExpression arg0) {
		
	}

	@Override
	public void visit(WhenClause arg0) {
		
	}

	@Override
	public void visit(ExistsExpression arg0) {
		
	}

	@Override
	public void visit(AllComparisonExpression arg0) {
		
	}

	@Override
	public void visit(AnyComparisonExpression arg0) {
		
	}

	@Override
	public void visit(Concat arg0) {
		
	}

	@Override
	public void visit(Matches arg0) {
		
	}

	@Override
	public void visit(BitwiseAnd arg0) {
		
	}

	@Override
	public void visit(BitwiseOr arg0) {
		
	}

	@Override
	public void visit(BitwiseXor arg0) {
		
	}

	@Override
	public void visit(CastExpression arg0) {
		
	}

	@Override
	public void visit(Modulo arg0) {
		
	}

	@Override
	public void visit(AnalyticExpression arg0) {
		
	}

	@Override
	public void visit(WithinGroupExpression arg0) {
		
	}

	@Override
	public void visit(ExtractExpression arg0) {
		
	}

	@Override
	public void visit(IntervalExpression arg0) {
		
	}

	@Override
	public void visit(OracleHierarchicalExpression arg0) {
		
	}

	@Override
	public void visit(RegExpMatchOperator arg0) {
		
	}

	@Override
	public void visit(JsonExpression arg0) {
		
	}

	@Override
	public void visit(RegExpMySQLOperator arg0) {
		
	}

	@Override
	public void visit(UserVariable arg0) {
		
	}

	@Override
	public void visit(NumericBind arg0) {
		
	}

	@Override
	public void visit(KeepExpression arg0) {
		
	}

}
